package demoMod.gdxform.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import demoMod.gdxform.abstracts.Container;
import demoMod.gdxform.core.AbstractAction;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.enums.GFrameWindowMode;
import demoMod.gdxform.enums.GFrameWindowStyle;
import demoMod.gdxform.interfaces.Element;
import demoMod.gdxform.interfaces.MouseEventSubscriber;
import sun.awt.shell.ShellFolder;

import java.awt.*;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class GFileSelectWindow extends GFrame {
    private final GTextField path;
    private final GList<FileItem> fileSelectList;
    private final GScrollPane scrollPane;
    private final GButton back;
    private final GButton confirm;
    private final GButton cancel;
    private final GTextField filename;
    private BiConsumer<String, List<File>> confirmCallback;
    private BiConsumer<String, List<File>> cancelCallback;
    static final Map<String, Texture> iconCache = new HashMap<>();
    private int maxSelectedFiles = 1;
    private String backText = "上一级";
    private String confirmText = "确定";
    private String cancelText = "取消";
    private FileFilter fileFilter = pathname -> true;
    private String[] fileRegex = new String[]{".*"};
    private String fileExtDescription = "所有文件";
    private PathNode currentPath;

    public GFileSelectWindow(BitmapFont bannerFont) {
        super(Gdx.graphics.getWidth() * 0.3F, Gdx.graphics.getHeight() * 0.25F, Gdx.graphics.getWidth() * 0.4F, Gdx.graphics.getHeight() * 0.5F, bannerFont, GFrameWindowStyle.CLOSE_BUTTON_ONLY, false);
        setResizable(false);
        setWindowMode(GFrameWindowMode.MODAL);
        fileSelectList = new GList<>();
        fileSelectList.setX(0);
        fileSelectList.setY(0);
        fileSelectList.setWidth(getWidth());
        fileSelectList.setHeight(getHeight() - getBannerHeight());
        fileSelectList.setColumns(3);
        fileSelectList.setCellWidth(Gdx.graphics.getWidth() * 0.1F);
        fileSelectList.setCellHeight(Gdx.graphics.getHeight() * 0.035F);
        fileSelectList.setMaxSelectedItems(maxSelectedFiles);
        fileSelectList.setBackground(new Color(0.16863F, 0.16863F, 0.16863F, 1));
        scrollPane = new GScrollPane(0, Gdx.graphics.getHeight() * 0.02F + 6, getWidth(), getHeight() - getBannerHeight() - Gdx.graphics.getHeight() * 0.05F - 6, getWidth(), getHeight() - getBannerHeight());
        scrollPane.setBackground(new Color(0.16863F, 0.16863F, 0.16863F, 1));
        if (fileSelectList.getColumns() * fileSelectList.getCellWidth() > getWidth()) {
            GHScrollBar hScrollBar = new GHScrollBar(0, 0, getWidth() * 0.9F, 10);
            scrollPane.setHScrollBar(hScrollBar);
        }
        GVScrollBar vScrollBar = new GVScrollBar(0, 0, 10, getHeight());
        scrollPane.setVScrollBar(vScrollBar);
        scrollPane.addElement(fileSelectList);
        addElement(scrollPane);
        path = new GTextField(0, getHeight() - getBannerHeight() - Gdx.graphics.getHeight() * 0.03F + 3, getWidth() * 0.9F - 3, Gdx.graphics.getHeight() * 0.03F - 6, bannerFont) {
            @Override
            public boolean keyDown(int keyCode) {
                if (keyCode == Input.Keys.ENTER) {
                    setPath(getText());
                    FormManager.getInstance().getEventHooks().setCurrentFocusedElement(getParent());
                }
                return super.keyDown(keyCode);
            }
        };
        addElement(path);
        back = new GButton(getWidth() * 0.9F + 3, getHeight() - getBannerHeight() - Gdx.graphics.getHeight() * 0.03F + 3, getWidth() * 0.1F - 6, Gdx.graphics.getHeight() * 0.03F - 6, bannerFont) {
            @Override
            public void onClick() {
                currentPath = currentPath.getParent();
                refreshFileList();
            }
        };
        back.setText(backText);
        back.setBackground(new Color(0, 47.0F / 255.0F, 147.0F / 255.0F, 1.0F));
        back.setBorderWidth(3);
        back.setBorder(Color.LIGHT_GRAY.cpy());
        back.setEnabled(false);
        addElement(back);
        confirm = new GButton(getWidth() * 0.7F, 3, getWidth() * 0.1F, Gdx.graphics.getHeight() * 0.02F, bannerFont) {
            @Override
            public void onClick() {
                List<File> param = fileSelectList.getSelectedItems().stream().map(FileItem::getFile).collect(Collectors.toList());
                if (confirmCallback != null) {
                    confirmCallback.accept((currentPath != null ? currentPath.buildPath() : "") + File.separator + filename.getText(), param);
                }
                FormManager.getInstance().removeContainer(GFileSelectWindow.this);
            }
        };
        confirm.setText(confirmText);
        confirm.setBackground(new Color(0, 47.0F / 255.0F, 147.0F / 255.0F, 1.0F));
        confirm.setBorderWidth(3);
        confirm.setBorder(Color.LIGHT_GRAY.cpy());
        cancel = new GButton(getWidth() * 0.85F + 9, 3, getWidth() * 0.1F, Gdx.graphics.getHeight() * 0.02F, bannerFont) {
            @Override
            public void onClick() {
                List<File> param = fileSelectList.getSelectedItems().stream().map(FileItem::getFile).collect(Collectors.toList());
                if (cancelCallback != null) {
                    cancelCallback.accept((currentPath != null ? currentPath.buildPath() : "") + File.separator + filename.getText(), param);
                }
                FormManager.getInstance().removeContainer(GFileSelectWindow.this);
            }
        };
        cancel.setText(cancelText);
        cancel.setBackground(Color.LIGHT_GRAY.cpy());
        cancel.setBorderWidth(3);
        cancel.setBorder(Color.LIGHT_GRAY.cpy());
        filename = new GTextField(0, 0, getWidth() * 0.6F - 3, Gdx.graphics.getHeight() * 0.02F, bannerFont);
        filename.setCharFilter("[a-zA-Z0-9,.;'\\[\\]{}\\s`~\\-=!@#$%^&()_+]");
        addElement(filename);
        addElement(confirm);
        addElement(cancel);
    }

    public void setPath(String path) {
        File file = new File(path);
        if (file.exists() && isPathnameValid(path)) {
            path = file.getAbsolutePath();
            path = path.replace(File.separator, "/");
            path = path.replace("//", "/");
            PathNode node = null;
            for (String s : path.split("/")) {
                if (s.trim().length() == 0) continue;
                PathNode token = new PathNode();
                token.setName(s);
                if (node != null) {
                    token.setParent(node);
                }
                node = token;
            }
            currentPath = node;
            if (!file.isDirectory()) {
                List<File> param = new ArrayList<>();
                param.add(file);
                if (confirmCallback != null) {
                    confirmCallback.accept((currentPath != null ? currentPath.buildPath() : "") + File.separator + filename.getText(), param);
                }
                this.path.setParent(null);
                FormManager.getInstance().removeContainer(this);
                return;
            }
            refreshFileList();
        }
    }

    private void refreshFileList() {
        for (Element element : fileSelectList.getElements()) {
            element.dispose();
        }
        fileSelectList.clear();
        if (currentPath != null) {
            String filepath = currentPath.buildPath();
            if (currentPath.getParent() == null) {
                filepath = filepath + File.separator;
            }
            File currentFolder = new File(filepath);
            File[] files = currentFolder.listFiles(this.fileFilter);
            if (files != null) {
                for (File file : files) {
                    FileItem fileItem = new FileItem();
                    fileItem.setFile(file);
                    fileSelectList.addItem(fileItem);
                }
                back.setEnabled(true);
                path.setText(currentPath.buildPath());
                float plainHeight = ((float) files.length / fileSelectList.getColumns() + 1) * fileSelectList.getCellHeight();
                scrollPane.setPlainHeight(Math.max(plainHeight, scrollPane.getHeight()));
            }
        } else {
            File[] files = File.listRoots();
            for (File file : files) {
                FileItem fileItem = new FileItem();
                fileItem.setFile(file);
                fileSelectList.addItem(fileItem);
            }
            back.setEnabled(false);
            path.setText("我的电脑");
            float plainHeight = ((float) files.length / fileSelectList.getColumns() + 1) * fileSelectList.getCellHeight();
            scrollPane.setPlainHeight(Math.max(plainHeight, scrollPane.getHeight()));
        }
    }

    private boolean isPathnameValid(String path) {
        String regex = "[a-zA-Z]:(?:[/\\\\][^/\\\\:*?\"<>|]{0,255})*";
        Pattern pattern = Pattern.compile(regex);
        return pattern.matcher(path).matches();
    }

    public void setConfirmCallback(BiConsumer<String, List<File>> confirmCallback) {
        this.confirmCallback = confirmCallback;
    }

    public void setCancelCallback(BiConsumer<String, List<File>> cancelCallback) {
        this.cancelCallback = cancelCallback;
    }

    public void setConfirmText(String confirmText) {
        this.confirmText = confirmText;
        this.confirm.setText(confirmText);
    }

    public void setCancelText(String cancelText) {
        this.cancelText = cancelText;
        this.cancel.setText(cancelText);
    }

    public void setBackText(String backText) {
        this.backText = backText;
        this.back.setText(backText);
    }

    public void setMaxSelectedFiles(int maxSelectedFiles) {
        this.maxSelectedFiles = maxSelectedFiles;
        fileSelectList.setMaxSelectedItems(maxSelectedFiles);
    }

    public void setFileFilter(FileFilter fileFilter) {
        this.fileFilter = fileFilter;
    }

    public void setFileExtDescription(String fileExtDescription) {
        this.fileExtDescription = fileExtDescription;
    }

    public static void clearIconCache() {
        for (Map.Entry<String, Texture> e : iconCache.entrySet()) {
            e.getValue().dispose();
        }
        iconCache.clear();
    }

    @Override
    public void dispose() {
        super.dispose();
        clearIconCache();
    }

    static class PathNode {
        private String name;
        private PathNode parent;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public PathNode getParent() {
            return parent;
        }

        public void setParent(PathNode parent) {
            this.parent = parent;
        }

        public String buildPath() {
            return getParent() != null ? getParent().buildPath() + File.separator + name : name;
        }
    }

    class FileItem implements Element, ImageConsumer, ImageObserver, MouseEventSubscriber {
        private String id;
        private Container<? extends Element> parent;
        private Texture icon;
        private Pixmap pixels;
        private File file;
        private ImageProducer imageProducer;
        private boolean mouseDown = false;

        @Override
        public String getId() {
            return id;
        }

        @Override
        public void setId(String id) {
            this.id = id;
        }

        private Texture getIcon() {
            try {
                ShellFolder shellFolder = ShellFolder.getShellFolder(file);
                Image image = shellFolder.getIcon(false); //true为大图标,false为小图标
                imageProducer = image.getSource();
                pixels = new Pixmap(image.getWidth(this), image.getHeight(this), Pixmap.Format.RGBA8888);
                imageProducer.startProduction(this);
                return new Texture(pixels);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

        public File getFile() {
            return file;
        }

        public void setFile(File file) {
            this.file = file;
            String fileExt = file.getName();
            if (file.isFile()) {
                String[] tokens = fileExt.split("\\.");
                fileExt = tokens[tokens.length - 1];
            } else {
                fileExt = "<folder>";
            }
            if (iconCache.size() > 1500) clearIconCache();
            icon = iconCache.get(fileExt);
            if (icon == null || fileExt.equals("exe")) {
                if (fileExt.equals("exe")) {
                    fileExt = file.getName();
                    if (icon != null) {
                        icon.dispose();
                    }
                }
                icon = getIcon();
                iconCache.put(fileExt, icon);
            }
        }

        @Override
        public void update() {

        }

        @Override
        public void render(SpriteBatch sb) {
            sb.setColor(Color.WHITE);
            BitmapFont font = GFileSelectWindow.this.getBannerFont();
            String filename = file.getName();
            if (filename.isEmpty()) filename = file.getAbsolutePath();
            if (this.icon != null) {
                sb.draw(this.icon, getX(true), getY(true), getHeight(), getHeight());
                font.draw(sb, filename, getX(true) + getHeight(), getY(true) + (font.getLineHeight() + getHeight()) / 2.0F);
            } else {
                font.draw(sb, filename, getX(true), getY(true) + (font.getLineHeight() + getHeight()) / 2.0F);
            }
        }

        @Override
        public Container<? extends Element> getParent() {
            return parent;
        }

        @Override
        public void setParent(Container<? extends Element> parent) {
            this.parent = parent;
        }

        @Override
        public float getX(boolean isAbsolute) {
            return getParent() != null ? getParent().getX(isAbsolute) : 0;
        }

        @Override
        public float getY(boolean isAbsolute) {
            return getParent() != null ? getParent().getY(isAbsolute) : 0;
        }

        @Override
        public void setX(float x) {

        }

        @Override
        public void setY(float y) {

        }

        @Override
        public float getWidth() {
            return getParent() != null ? getParent().getWidth() : Gdx.graphics.getWidth();
        }

        @Override
        public float getHeight() {
            return getParent() != null ? getParent().getHeight() : Gdx.graphics.getHeight();
        }

        @Override
        public void setWidth(float width) {

        }

        @Override
        public void setHeight(float height) {

        }

        @Override
        public Color getBackground() {
            return null;
        }

        @Override
        public void setBackground(Color background) {

        }

        @Override
        public boolean isResizable() {
            return false;
        }

        @Override
        public void setResizable(boolean resizable) {

        }

        @Override
        public boolean enabled() {
            return true;
        }

        @Override
        public void setEnabled(boolean enabled) {

        }

        @Override
        public boolean visible() {
            return true;
        }

        @Override
        public void setVisible(boolean visible) {

        }

        @Override
        public void dispose() {

        }

        @Override
        public void setDimensions(int width, int height) {

        }

        @Override
        public void setProperties(Hashtable<?, ?> props) {

        }

        @Override
        public void setColorModel(ColorModel model) {

        }

        @Override
        public void setHints(int hintflags) {

        }

        @Override
        public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {
            int colNum = 0;
            for (int i : pixels) {
                Color color = new Color();
                color.r = model.getRed(i) / 255.0F;
                color.g = model.getGreen(i) / 255.0F;
                color.b = model.getBlue(i) / 255.0F;
                color.a = model.getAlpha(i) / 255.0F;
                this.pixels.setColor(color);
                this.pixels.drawPixel(colNum, y);
                colNum++;
            }
        }

        @Override
        public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {
            int colNum = 0;
            for (int i : pixels) {
                Color color = new Color();
                color.r = model.getRed(i) / 255.0F;
                color.g = model.getGreen(i) / 255.0F;
                color.b = model.getBlue(i) / 255.0F;
                color.a = model.getAlpha(i) / 255.0F;
                this.pixels.setColor(color);
                this.pixels.drawPixel(colNum, y);
                colNum++;
            }
        }

        @Override
        public void imageComplete(int status) {
            if (status == ImageConsumer.STATICIMAGEDONE && imageProducer != null) {
                imageProducer.removeConsumer(this);
            }
        }

        @Override
        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
            return false;
        }

        @Override
        public boolean clickDown(int screenX, int screenY, int button) {
            if (!this.visible()) return false;
            mouseDown = screenX >= getX(true) && screenX <= getX(true) + getWidth() &&
                    Gdx.graphics.getHeight() - screenY >= getY(true) && Gdx.graphics.getHeight() - screenY <= getY(true) + getHeight();
            if (mouseDown && ((GList.ListCellRenderer<FileItem>)getParent()).isSelected() && file.isDirectory()) {
                PathNode node = new PathNode();
                node.setName(file.getName().equals("") ? file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 1) : file.getName());
                node.setParent(currentPath);
                currentPath = node;
                refreshFileList();
            } else if (mouseDown && ((GList.ListCellRenderer<FileItem>)getParent()).isSelected() && !file.isDirectory()) {
                List<File> param = fileSelectList.getSelectedItems().stream().map(FileItem::getFile).collect(Collectors.toList());
                if (confirmCallback != null) {
                    confirmCallback.accept(currentPath != null ? currentPath.buildPath() : "", param);
                }
                FormManager.getInstance().removeContainer(GFileSelectWindow.this);
            }
            if (mouseDown && !file.isDirectory()) {
                filename.setText(file.getName());
            }
            return mouseDown;
        }

        @Override
        public boolean clickUp(int screenX, int screenY, int button) {
            return false;
        }

        @Override
        public boolean mouseDragged(int screenX, int screenY) {
            return false;
        }

        @Override
        public boolean mouseMoved(int screenX, int screenY) {
            return false;
        }

        @Override
        public boolean scrolled(int amount) {
            return false;
        }

        @Override
        public boolean moveToElementBorder(Element element) {
            return false;
        }
    }
}
